/**
 * 公众号粉丝消息和事件被动回复
 * @authors 杨兴洲（of2502）
 * @date    2017/3/7 13:58
 * @version 1.0
 */
// todo: 代码整理，格式太乱。interface??

import cryptor from "../util/cryptor-util";
import {getReqRawBody} from "../util/request-util";
import XMLUtil from "../util/xml-util";
import logger from "../util/logger";
import AutoReplyService from "../service/reply-service";
import MessageService from "../service/message-service";
import WxCustomService from "../service/weixin-custom-service";
import WxAuthorizerService from "../service/weixin-authorizer-service";
import UserService from "../service/user-service";
import AdvancedNewsService from "../service/advancedNews-service";
import MCService from "../service/mc-service";

/**
 * 接收消息和事件
 */
export default(req, res, next) => {
    (async () => {
        try {
            const appID = req.params.appID;
            const receiveMsg = await parseMessage(req);
            logger.debug(receiveMsg);
            //--------------------------------过滤微信测试账号---------------------------
            // appid: wx570bc396a51b8ff8; username: gh_485e62956e5d (文档提供)
            // appid: wx570bc396a51b8ff8; username: gh_3c884a361561 (实际提供)
            const isWxTest = receiveMsg.ToUserName === "gh_3c884a361561";
            if (isWxTest) {
                let compiledMsg = await handleWxTestMessage(receiveMsg);
                return res.end(compiledMsg || "success");
            }

            //-------------------------默认先响应请求,在处理消息---------------------------
            res.end("success");

            //-------------------------handle receive msg------------------------------
            // 存储消息
            let messageID = await MessageService.save(messageAdapter(receiveMsg, appID));

            /* 推送给营销中心 */
            const pushToMarketCenter = async (message) => {
                const {MsgType, Event, FromUserName: openId,EventKey} = message;
                if (MsgType !== "event") return;
                if (Event === "subscribe") {// 关注.推送给营销中心
                    let salesCode = '';
                    if(EventKey){
                        salesCode = EventKey.split('_')[1];//二维码的参数值
                        logger.info('粉丝扫描参数二维码关注，salesCode为--->>',salesCode);
                    }
                    return await MCService.batchFollow({appID, openIds: [openId],salesCode});
                }
                if(Event === 'SCAN'){
                    //用户已关注公众号 ， 再扫描参数二维码的推送
                    logger.info('已关注粉丝扫描参数二维码，EventKey为--->>',EventKey);
                    return await MCService.batchFollow({appID, openIds: [openId],salesCode:EventKey});
                }
                if (Event === "unsubscribe") {// 取消关注.推送给营销中心
                    // return await MCService.batchUnfollow({appID, openIds: [openId]});
                }

            };
            pushToMarketCenter(receiveMsg)
                .then(data => console.log("粉丝消息推送给MC", data))
                .catch(err => console.log(err));

            //------------------------------发送消息------------------------------------

            // 获取自动回复规则
            let reply = await handleReceiveMessage(appID, receiveMsg);
            if (!reply) reply = [];
            if (!Array.isArray(reply)) reply = [reply]; //方便兼容多消息处理(Promise.all)
            logger.debug("reply:::", reply);

            // appID => userID => access_token
            const {userID} = await UserService.getInfoByAppID(appID);
            const access_token = await UserService.getAccessToken(userID);

            // 客服主动推送消息相关处理
            // 发送 => 保存(新) => 更新(老)
            const sendAndSaveCustomMsg = async (data) => {
                let message: any = await createCustomMsg({data, touser: receiveMsg.FromUserName});
                logger.debug(JSON.stringify(message));
                await WxCustomService.sendMessage({access_token, message});
                // 保存消息
                message.msgType = message.msgtype;
                message.toUser = message.touser;
                message.fromUser = appID;
                let replyFromID = await MessageService.save(message);
                logger.debug("save", replyFromID);
                // 插入(更新消息列表)
                await MessageService.updateReplyList({replyFrom: replyFromID, replyTo: messageID}); // 更新
            };
            // 批量发生消息(可以不用等待)
            await Promise.all(reply.map(sendAndSaveCustomMsg));


        } catch (error) {
            logger.error(error);
        }
    })();
}

/**
 * 消息适配器
 * 微信推送消息(XML-JSON) => 通用消息格式(JSON)
 * @param receiveMsg
 * @param appID
 * @returns {any}
 */
function messageAdapter(receiveMsg, appID) {
    let message: any = {
        toUser: appID,
        fromUser: receiveMsg.FromUserName,
        msgType: receiveMsg.MsgType
    };
    if (receiveMsg.MsgType === "text") {
        message[receiveMsg.MsgType] = {
            content: receiveMsg.Content
        };
    }
    if (receiveMsg.MsgType === "image") {
        message[receiveMsg.MsgType] = {
            media_id: receiveMsg.MediaId,
            pic_url: receiveMsg.PicUrl,
        };

    }
    if (receiveMsg.MsgType === "voice") {
        message[receiveMsg.MsgType] = {
            media_id: receiveMsg.MediaId,
            format: receiveMsg.Format,
        };
    }
    if (receiveMsg.MsgType === "video") {
        message[receiveMsg.MsgType] = {
            media_id: receiveMsg.MediaId,
            thumb_media_id: receiveMsg.ThumbMediaId,
        };
    }
    logger.debug(message);
    return message;
}


/**
 * 0 解析微信推送信息
 * 1.获取 2.XML => JSON 3.decrypt 5.XML => JSON
 * @param req
 * @returns {Promise<message>}
 */
async function parseMessage(req: any): Promise<ReceiveMsg | any> {
    let rawBody = await getReqRawBody(req);
    let result = await XMLUtil.parse2Json(rawBody);
    logger.debug(result);
    let data = cryptor.decrypt(result['Encrypt']);
    return await XMLUtil.parse2Json(data.message);
}

/**
 * 1.1 处理微信全网发布测试消息
 * @param message 接受到的解密后的消息
 * @returns {Promise<string>}
 */
async function handleWxTestMessage(message) {
    try {
        if (message.MsgType === "event") {
            return compileMessage(createJSONMsg({
                type: "text",
                content: message.Event + "from_callback"
            }, message));
        }
        if (message.MsgType === "text") {
            if (message.Content === "TESTCOMPONENT_MSG_TYPE_TEXT") {
                // todo 重构Message
                // message.createJSON().compile() 更加好看
                return compileMessage(createJSONMsg({
                    type: "text",
                    content: "TESTCOMPONENT_MSG_TYPE_TEXT_callback"
                }, message))
            }
            if (message.Content.indexOf("QUERY_AUTH_CODE") > -1) {
                const auth_code = message.Content.split(":")[1];
                let result: any = await WxAuthorizerService.queryAuth(auth_code);
                let reply = {
                    access_token: result.authorization_info.authorizer_access_token,
                    message: {
                        touser: message.FromUserName,
                        msgtype: "text",
                        text: {
                            content: auth_code + "_from_api"
                        }
                    }
                };
                logger.debug('--------------------------------');
                logger.debug(auth_code, auth_code + "_from_api");
                logger.debug(reply);
                logger.debug('--------------------------------');
                await WxCustomService.sendMessage(reply);
            }
        }
    } catch (err) {
        logger.error(JSON.stringify(err))
    }

}


/**
 * 1. 处理接受的消息
 * @param appID
 * @param message
 * @returns {Promise<void>}
 */
async function handleReceiveMessage(appID, message) {
    const {MsgType, Event, EventKey, Content} = message;

    switch (MsgType) {
        case 'event':
            return handleEventMsg({appID, Event, EventKey});
        case 'text':
            // 关键词匹配 $match value || 正则
            return await AutoReplyService.getKeywordRuleByText({appID, text: Content});
        case 'image':
            break;
        case 'video':
            break;
        case 'shortvideo':
            break;
        case 'location':
            break;
        case 'link':
            break;
        default :// 不作处理
    }
}

/**
 * 1.1 处理接受的事件类型消息
 * @param appID
 * @param Event
 * @param EventKey
 * @returns {Promise<{type: string, content: string}>}
 */
async function handleEventMsg({appID, Event, EventKey}) {
    switch (Event) {
        case "subscribe": // 关注后,回复关键词
            // 取消关注.推送给营销中心

            const {add_friend_autoreply_info} = await AutoReplyService.getRules(appID);
            return add_friend_autoreply_info;
        case "unsubscribe":
            // 取消关注.推送给营销中心
            break;
        case "CLICK":
            if (!EventKey) return;
            const type = EventKey.split("__")[0];
            const content = EventKey.split("__")[1];
            if (!type || !content) {
                logger.error("EventKey不正确,无法获取到type/content");
                return;
            }
            // 返回客服消息
            return {type, content};
        case "SCAN": // 参数二维码(EventKey)
        case "LOCATION":// 定位相关
        case "VIEW": // media_ID
            break;
        default :// 不作处理
    }
}

/**
 * 2 编译消息 | 生成客服Message
 * 消息主体 => 客服消息(含有回复对象)
 * @param data 回复内容
 * @param touser 用户openid
 * @returns {Promise<void>}
 */
async function createCustomMsg({data, touser}): Promise<CustomMsg> {
    let {type, content, ...rest} = data;

    const message: any = {touser, msgtype: type};
    // 文本
    if (type === "text") {
        // page__http://sdfs.wd.1000.com/
        const tp = content.split("__")[0];
        if (["page", "custom", "goods"].includes(tp)) { // 兼容链接
            message[data.type] = {content: content.slice(tp.length + 2, content.length)}
        } else {
            message[data.type] = {content: content};
        }
    }

    // 图片/语音/图文
    if (["image", "voice", "mpnews"].includes(type)) {
        message[data.type] = {media_id: content};
    }

    // 其他类型等待处理
    // message[data.type] = {...rest};

    // 视频
    if (type === "video") {
        let {title, description, thumb_media_id} = rest;
        message[data.type] = {media_id: content, title, description, thumb_media_id};
    }

    // 图文形式,可以外链
    if (type === "news") {
        // articles: [{title,description,url,picurl}]
        let {articles} = await AdvancedNewsService.getDetail(content);
        if (!articles) return;
        articles = articles.map(item => {
            const {title, content_source_url, thumb_url, digest} = item;
            return ({
                title,
                description: digest,
                url: content_source_url,
                picurl: thumb_url
            });
        });
        // 转化数据
        message[data.type] = {articles};
    }

    // 音乐
    if (type === "music") {
        let {title, description, thumb_media_id, musicurl, hqmusicurl} = rest;
        message[data.type] = {title, description, thumb_media_id, musicurl, hqmusicurl};
    }

    // 卡券
    if (type === "wxcard") {
        let {card_id} = rest;
        message[data.type] = {card_id};
    }

    return message;
}


/**
 * 2 编译消息 | 被动回复消息
 * 废弃： 走客服接口
 * JSON => XML => encrypt => XML
 * @param message
 * @returns {string}
 */
function compileMessage(message) {
    const XML = createXMLMessage(message);
    const encryptMessage = cryptor.encrypt(XML);
    return wrapMessage({encryptMessage});
}

/**
 * 2.1 根据回复规则，生成回复JSON数据
 * @param reply
 * @param receiveMsg
 * @returns {ReplyMsg}
 */
function createJSONMsg(reply, receiveMsg) {
    // 单条回复
    let replyMsg: ReplyMsg = {
        toUserName: receiveMsg.FromUserName,
        fromUserName: receiveMsg.ToUserName,
        createTime: Math.floor(+new Date() / 1000),
        msgType: reply.type,
        [reply.type]: reply.content
    };
    if (reply.type === "text") {
        replyMsg.content = reply.content;
    }
    if (["image", "voice", "video"].includes(reply.type)) {
        replyMsg.mediaId = reply.content;
    }
    if (reply.type === "video") {
        replyMsg.title = reply.title;
        replyMsg.description = reply.description;
    }
    if (reply.type === "news") {
        // 图文详情
    }
    if (reply.type === "music") {
        // 音乐详情
    }
    return replyMsg;
}

/**
 * 2.2 生成MessageXML
 * @param message
 */
function createXMLMessage(message: ReplyMsg): string {
    return `
        <xml>
            <ToUserName><![CDATA[${message.toUserName}]]></ToUserName>
            <FromUserName><![CDATA[${message.fromUserName}]]></FromUserName>
            <CreateTime>${message.createTime}</CreateTime>
            <MsgType><![CDATA[${message.msgType}]]></MsgType>
            ${createXMLContent(message)}
        </xml>`;

    /**
     * MsgType => Content
     * @param message
     * @returns {string}
     */
    function createXMLContent(message: ReplyMsg): string {
        let content = '';
        switch (message.msgType) {
            case 'text':
                content = `<Content><![CDATA[${message.content}]]></Content>`;
                break;
            case 'image':
                content = `<Image><MediaId><![CDATA[${message.mediaId}]]></MediaId></Image>`;
                break;
            case 'voice':
                content = `<Voice><MediaId><![CDATA[${message.mediaId}]]></MediaId></Voice>`;
                break;
            case 'video':
                content = `
                    <Video>
                        <MediaId><![CDATA[${message.mediaId}]]></MediaId>
                        <Title><![CDATA[${message.title}]]></Title>
                        <Description><![CDATA[${message.description}]]></Description>
                    </Video>`;
                break;
            case 'music':
                content = `
                <Music>
                    <Title><![CDATA[${message.title}]]></Title>
                    <Description><![CDATA[${message.description}]]></Description>
                    <MusicUrl><![CDATA[${message.musicUrl}]]></MusicUrl>
                    <HQMusicUrl><![CDATA[${message.HQMusicUrl}]]></HQMusicUrl>
                    <ThumbMediaId><![CDATA[${message.thumbMediaId}]]></ThumbMediaId>
                </Music>`;
                break;
            case 'news':
                let articlesXML = message.articles.map((article) => {
                    return `
                        <item>
                            <Title><![CDATA[${article.title}]]></Title> 
                            <Description><![CDATA[${article.description}]]></Description>
                            <PicUrl><![CDATA[${article.picUrl}]]></PicUrl>
                            <Url><![CDATA[${article.url}]]></Url>
                        </item>`;
                }).join("");
                content = `
                    <ArticleCount>${message.articles.length}</ArticleCount>
                    <Articles>${articlesXML}</Articles>`;
                break;
            default:
                throw new Error("回复消息类型错误!")
        }

        return content;
    }
}


/**
 * 2.3 生成最后的res的XML
 * @param timestamp
 * @param nonce
 * @param encryptMessage
 * @returns {string}
 */
function wrapMessage({encryptMessage, timestamp = Math.floor(+new Date() / 1000), nonce = Math.floor(Math.random() * 1000000000)}: any) {
    const msgSignature = cryptor.getSignature(timestamp, nonce, encryptMessage);
    return `
       <xml>
          <Encrypt><![CDATA[${encryptMessage}]]></Encrypt>
          <MsgSignature><![CDATA[${msgSignature}]]></MsgSignature>
          <TimeStamp>${timestamp}</TimeStamp>
          <Nonce><![CDATA[${nonce}]]></Nonce>
       </xml>`;
}

/*===================================interface===============================================*/

/**
 * 用户发送消息格式
 */
type receiveEventType = 'subscribe' | 'unsubscribe' | 'SCAN' | 'LOCATION' | 'CLICK' | 'VIEW';
type receiveMsgType = 'event' | 'text' | 'image' | 'video' | 'shortvideo' | 'location' | 'link';
interface ReceiveMsg {
    ToUserName: string;
    FromUserName: string;
    CreateTime: string;
    MsgType: receiveMsgType;
    // -------------msg-----------
    MsgId: string;
    Content: string // text
    PicUrl: string; // image
    ThumbMediaId?: string;// video，shortvideo
    Format?: string; // voice
    // -----------event------------
    Event?: receiveEventType;
    EventKey?: string; // subscribe, SCAN, CLICK ,VIEW
    Ticket?: string; // subscribe, SCAN,
    Latitude?: string; // LOCATION
    Longitude?: string;	// LOCATION
    Precision?: string; // LOCATION
}

/**
 * 被动回复消息格式
 */

type ReplyMsgType = 'text' | 'image' | 'voice' | 'video' | 'music' | 'news';
type MediaId = string;

interface ReplyMsg {
    toUserName: string;
    fromUserName: string;
    createTime: number;
    msgType: ReplyMsgType;
    content?: string;
    mediaId?: MediaId;
    title?: string; // video,Music,Article
    description ?: string; // video,Music,Article
    musicUrl?: string; // Music
    HQMusicUrl?: string; // Music
    thumbMediaId?: MediaId;
    articleCount?: number; // article
    articles?: { //article
        title?: string;
        description?: string;
        picUrl?: string;
        url?: string;
    }[];

}

// type replyMsgType = 'text' | 'image' | 'voice' | 'video' | 'music' | 'news';
// type MediaId = string;
//
// interface ReplyMsg {
//     ToUserName: string;
//     FromUserName: string;
//     CreateTime: number;
//     MsgType: replyMsgType;
// }
//
// interface ReplyMsgText extends ReplyMsg {
//     Content: string;
// }
//
// interface ReplyMsgImage extends ReplyMsg {
//     MediaId: MediaId;
// }
//
// interface ReplyMsgVoice extends ReplyMsg {
//     MediaId: MediaId;
// }
//
// interface ReplyMsgVideo extends ReplyMsg {
//     MediaId: MediaId;
//     Title?: string;
//     Description ?: string;
// }
//
// interface ReplyMsgMusic extends ReplyMsg {
//     Title?: string;
//     Description?: string;
//     MusicUrl?: string;
//     HQMusicUrl?: string;
//     ThumbMediaId?: MediaId;
// }
//
// interface Article extends ReplyMsg {
//     Title?: string;
//     Description?: string;
//     PicUrl?: string;
//     Url?: string;
// }
//
// interface ReplyMsgNews extends ReplyMsg {
//     ArticleCount: number; // <10
//     Articles: Article[];
// }
//
// type ReplyMsgs = ReplyMsgText & ReplyMsgImage & ReplyMsgVoice & ReplyMsgVideo & ReplyMsgNews & ReplyMsgMusic;
